/*
 * Loongson1 spi driver
 *
 * based on bfin_spi.c
 * Copyright (c) 2005-2008 Analog Devices Inc.
 * Copyright (C) 2014 Tang Haifeng <tanghaifeng-gz@loongson.cn>
 *
 * SPDX-License-Identifier:	GPL-2.0+
 */

#include <common.h>
#include <dm.h>
#include <asm/io.h>
#include <malloc.h>
#include <spi.h>
#include <asm/gpio.h>

#if defined(CONFIG_MACH_LOONGSON1)
#include <mach/ls1x.h>
#define LS_SPI0_BASE LS1X_SPI0_BASE
#define LS_SPI1_BASE LS1X_SPI1_BASE
#elif defined(CONFIG_MACH_LOONGSON2_SOC)
#include <mach/loongson.h>
#define LS_SPI0_BASE (CKSEG1ADDR(LS2X_SPI0_BASE))
#elif defined(CONFIG_MACH_LOONGSON3)
#include <mach/ls3x.h>
#define LS_SPI0_BASE (CKSEG1ADDR(LS3X_SPI0_BASE))
#endif

DECLARE_GLOBAL_DATA_PTR;

#ifndef CONFIG_LS1X_SPI_IDLE_VAL
# define CONFIG_LS1X_SPI_IDLE_VAL 0xff
#endif

#if defined(CONFIG_SPI_CS_USED_GPIO)
static int ls1x_cs_gpio [] = {
#ifdef SPI0_CS0_GPIO
	SPI0_CS0_GPIO,
#endif
#ifdef SPI0_CS1_GPIO
	SPI0_CS1_GPIO,
#endif
#ifdef SPI0_CS2_GPIO
	SPI0_CS2_GPIO,
#endif
#ifdef SPI0_CS3_GPIO
	SPI0_CS3_GPIO,
#endif
#ifdef SPI1_CS0_GPIO
	SPI1_CS0_GPIO,
#endif
#ifdef SPI1_CS1_GPIO
	SPI1_CS1_GPIO,
#endif
#ifdef SPI1_CS2_GPIO
	SPI1_CS2_GPIO,
#endif
#ifdef SPI1_CS3_GPIO
	SPI1_CS3_GPIO,
#endif
};
#endif

struct ls1x_spi_regs {
	unsigned char spcr;
	unsigned char spsr;
	unsigned char fifo;	/* TX/Rx data reg */
	unsigned char sper;
	unsigned char param;
	unsigned char softcs;
	unsigned char timing;
};

static inline void ls1x_spi_wait_rxe(struct ls1x_spi_regs *regs)
{
	u8 ret;

	ret = readb(&regs->spsr);
	ret = ret | 0x80;
	writeb(ret, &regs->spsr);	/* Int Clear */

	ret = readb(&regs->spsr);
	if (ret & 0x40) {
		writeb(ret & 0xbf, &regs->spsr);	/* Write-Collision Clear */
	}
}

static inline void ls1x_spi_wait_txe(struct ls1x_spi_regs *regs)
{
	int timeout = 20000;

	while (timeout) {
		if (readb(&regs->spsr) & 0x80) {
			break;
		}
		timeout--;
	}
}

#ifndef CONFIG_DM_SPI

struct ls1x_spi_host {
	uint base;
	uint freq;
	uint baudwidth;
};

static const struct ls1x_spi_host ls1x_spi_host_list[] = {
	{
		.base = LS_SPI0_BASE,
	},
#if defined(CONFIG_MACH_LOONGSON1)
	{
		.base = LS_SPI1_BASE,
	},
#endif
};

struct ls1x_spi_slave {
	struct spi_slave slave;
	const struct ls1x_spi_host *host;
	uint mode;
	uint div;
	uint flg;
};
#define to_ls1x_spi_slave(s) container_of(s, struct ls1x_spi_slave, slave)

static int inited = 0;

int spi_cs_is_valid(unsigned int bus, unsigned int cs)
{
#if defined(CONFIG_SPI_CS_USED_GPIO)
//	return bus < ARRAY_SIZE(ls1x_spi_host_list) && gpio_is_valid(cs);
	return bus < ARRAY_SIZE(ls1x_spi_host_list);
#elif defined(CONFIG_SPI_CS)
	return bus < ARRAY_SIZE(ls1x_spi_host_list);
#endif
}

void spi_cs_activate(struct spi_slave *slave)
{
	struct ls1x_spi_slave *ls1x_spi = to_ls1x_spi_slave(slave);
	unsigned int cs = slave->cs;

#if defined(CONFIG_SPI_CS_USED_GPIO)
	gpio_set_value(cs, ls1x_spi->flg);
#elif defined(CONFIG_SPI_CS)
	{
	struct ls1x_spi_regs *regs = (void *)ls1x_spi->host->base;
	u8 ret;

	if (ls1x_spi->flg) {
		writeb(0x0f, &regs->softcs);
		ret = readb(&regs->softcs);
		ret = ret | (0x10 << cs);
		writeb(ret, &regs->softcs);
	} else {
		writeb(0xff, &regs->softcs);
		ret = readb(&regs->softcs);
		ret = ret & (~(0x10 << cs));
		writeb(ret, &regs->softcs);
	}
	}
	}
#endif
}

void spi_cs_deactivate(struct spi_slave *slave)
{
	struct ls1x_spi_slave *ls1x_spi = to_ls1x_spi_slave(slave);

#if defined(CONFIG_SPI_CS_USED_GPIO)
	{
	unsigned int cs = slave->cs;
	gpio_set_value(cs, !ls1x_spi->flg);
	}
#elif defined(CONFIG_SPI_CS)
	{
	struct ls1x_spi_regs *regs = (void *)ls1x_spi->host->base;

	if (ls1x_spi->flg) {
		writeb(0x0f, &regs->softcs);
	} else {
		writeb(0xff, &regs->softcs);
	}
	}
#endif
}

void spi_set_speed(struct spi_slave *slave, uint hz)
{
	struct ls1x_spi_slave *ls1x_spi = to_ls1x_spi_slave(slave);

	unsigned int div, div_tmp, bit;
	unsigned long clk;

#if defined(CONFIG_CPU_LOONGSON2K1000)
	clk = OSC_CLK;
#else
	clk = gd->bus_clk;
#endif
	div = DIV_ROUND_UP(clk, hz);

	if (div < 2)
		div = 2;

	if (div > 4096)
		div = 4096;

	bit = fls(div) - 1;
	switch(1 << bit) {
		case 16:
			div_tmp = 2;
			if (div > (1<<bit)) {
				div_tmp++;
			}
			break;
		case 32:
			div_tmp = 3;
			if (div > (1<<bit)) {
				div_tmp += 2;
			}
			break;
		case 8:
			div_tmp = 4;
			if (div > (1<<bit)) {
				div_tmp -= 2;
			}
			break;
		default:
			div_tmp = bit - 1;
			if (div > (1<<bit)) {
				div_tmp++;
			}
			break;
	}
//	debug("clk = %ld hz = %d div_tmp = %d bit = %d\n", clk, hz, div_tmp, bit);

	ls1x_spi->div = div_tmp;
}

void spi_init(void)
{
	const struct ls1x_spi_host *host;
	struct ls1x_spi_regs *regs;
	int i;
	u8 val;

	if (inited) {
		inited = 1;
		return;
	}

	for (i = 0; i < ARRAY_SIZE(ls1x_spi_host_list); i += 1) {
		host = &ls1x_spi_host_list[i];
		regs = (void *)host->base;
		/* 使能SPI控制器，master模式，关闭中断 */
		writeb(0x53, &regs->spcr);
		/* 清空状态寄存器 */
		writeb(0xc0, &regs->spsr);
		/* 1字节产生中断，采样(读)与发送(写)时机同时 */
		writeb(0x03, &regs->sper);
	#if defined(CONFIG_SPI_CS_USED_GPIO)
		writeb(0x00, &regs->softcs);
	#elif defined(CONFIG_SPI_CS)
		writeb(0xff, &regs->softcs);
	#endif
		/* 关闭SPI flash */
		val = readb(&regs->param);
		val &= 0xfe;
		writeb(val, &regs->param);
		/* SPI flash时序控制寄存器 */
		writeb(0x05, &regs->timing);
	}

#if defined(CONFIG_SPI_CS_USED_GPIO)
	{
	int i;
	char name[10];
	for (i = 0; i < ARRAY_SIZE(ls1x_cs_gpio); i += 1) {
		snprintf(name, sizeof(name), "ls1x_spi%01d", i);
		gpio_request(ls1x_cs_gpio[i], name);
	}
	}
#endif
#if defined(CONFIG_LS1X_SPI1_ENABLE) && defined(CONFIG_CPU_LOONGSON1B)
	writel(readl(LS1X_MUX_CTRL1) | SPI1_USE_CAN | SPI1_CS_USE_PWM01, LS1X_MUX_CTRL1);
#endif
}

struct spi_slave *spi_setup_slave(unsigned int bus, unsigned int cs,
				  unsigned int hz, unsigned int mode)
{
	struct ls1x_spi_slave *ls1x_spi;

	if (!spi_cs_is_valid(bus, cs))
		return NULL;

	ls1x_spi = spi_alloc_slave(struct ls1x_spi_slave, bus, cs);
	if (!ls1x_spi)
		return NULL;

	ls1x_spi->host = &ls1x_spi_host_list[bus];
	ls1x_spi->mode = mode & (SPI_CPOL | SPI_CPHA);
	ls1x_spi->flg = mode & SPI_CS_HIGH ? 1 : 0;
	spi_set_speed(&ls1x_spi->slave, hz);

	spi_init();

//	debug("%s: bus:%i cs:%i base:%x\n", __func__, bus, cs, ls1x_spi->host->base);

	return &ls1x_spi->slave;
}

void spi_free_slave(struct spi_slave *slave)
{
	struct ls1x_spi_slave *ls1x_spi = to_ls1x_spi_slave(slave);

	free(ls1x_spi);
}

int spi_claim_bus(struct spi_slave *slave)
{
	struct ls1x_spi_slave *ls1x_spi = to_ls1x_spi_slave(slave);
	struct ls1x_spi_regs *regs = (void *)ls1x_spi->host->base;
	u8 ret;

//	debug("%s: bus:%i cs:%i\n", __func__, slave->bus, slave->cs);
#if defined(CONFIG_SPI_CS_USED_GPIO)
	gpio_direction_output(slave->cs, !ls1x_spi->flg);
#endif
	ret = readb(&regs->spcr);
	ret = ret & 0xf0;
	ret = ret | (ls1x_spi->mode << 2) | (ls1x_spi->div & 0x03);
	writeb(ret, &regs->spcr);

	ret = readb(&regs->sper);
	ret = ret & 0xfc;
	ret = ret | (ls1x_spi->div >> 2);
	writeb(ret, &regs->sper);

	return 0;
}

void spi_release_bus(struct spi_slave *slave)
{
//	debug("%s: bus:%i cs:%i\n", __func__, slave->bus, slave->cs);
}

int spi_xfer(struct spi_slave *slave, unsigned int bitlen, const void *dout,
	     void *din, unsigned long flags)
{
	struct ls1x_spi_slave *ls1x_spi = to_ls1x_spi_slave(slave);
	struct ls1x_spi_regs *regs = (void *)ls1x_spi->host->base;
	const u8 *txp = dout;
	u8 *rxp = din;
	uint bytes = bitlen / 8;
	uint i;

//	debug("%s: bus:%i cs:%i bitlen:%i bytes:%i flags:%lx\n", __func__,
//		slave->bus, slave->cs, bitlen, bytes, flags);
	if (bitlen == 0)
		goto done;

	/* assume to do 8 bits transfers */
	if (bitlen % 8) {
		flags |= SPI_XFER_END;
		goto done;
	}

	if (flags & SPI_XFER_BEGIN)
		spi_cs_activate(slave);

	/* we need to tighten the transfer loop */
	if (txp && rxp) {
		for (i = 0; i < bytes; i += 1) {
			writeb(*txp++, &regs->fifo);
			ls1x_spi_wait_txe(regs);
			*rxp++ = readb(&regs->fifo);
			ls1x_spi_wait_rxe(regs);
		}
	} else if (rxp) {
		for (i = 0; i < bytes; i += 1) {
			writeb(CONFIG_LS1X_SPI_IDLE_VAL, &regs->fifo);
			ls1x_spi_wait_txe(regs);
			*rxp++ = readb(&regs->fifo);
			ls1x_spi_wait_rxe(regs);
		}
	} else if (txp) {
		for (i = 0; i < bytes; i += 1) {
			writeb(*txp++, &regs->fifo);
			ls1x_spi_wait_txe(regs);
			readb(&regs->fifo);
			ls1x_spi_wait_rxe(regs);
		}
	} else {
		for (i = 0; i < bytes; i += 1) {
			writeb(CONFIG_LS1X_SPI_IDLE_VAL, &regs->fifo);
			ls1x_spi_wait_txe(regs);
			readb(&regs->fifo);
			ls1x_spi_wait_rxe(regs);
		}
	}

 done:
	if (flags & SPI_XFER_END)
		spi_cs_deactivate(slave);

	return 0;
}

#else

struct ls1x_spi_platdata {
	struct ls1x_spi_regs *regs;
};

struct ls1x_spi_priv {
	struct ls1x_spi_regs *regs;
	uint mode;
	uint div;
	uint flg;
};

static void ls1x_spi_cs_activate(struct udevice *dev)
{
	struct udevice *bus = dev_get_parent(dev);
	struct ls1x_spi_priv *priv = dev_get_priv(bus);
	struct dm_spi_slave_platdata *slave_plat = dev_get_parent_platdata(dev);
	u32 cs = slave_plat->cs;

#if defined(CONFIG_SPI_CS_USED_GPIO)
	gpio_set_value(cs, priv->flg);
#elif defined(CONFIG_SPI_CS)
	{
	struct ls1x_spi_regs *const regs = priv->regs;
	u8 ret;

	if (priv->flg) {
		writeb(0x0f, &regs->softcs);
		ret = readb(&regs->softcs);
		ret = ret | (0x10 << cs);
		writeb(ret, &regs->softcs);
	} else {
		writeb(0xff, &regs->softcs);
		ret = readb(&regs->softcs);
		ret = ret & (~(0x10 << cs));
		writeb(ret, &regs->softcs);
	}
	}
#endif
}

static void ls1x_spi_cs_deactivate(struct udevice *dev)
{
	struct udevice *bus = dev_get_parent(dev);
	struct ls1x_spi_priv *priv = dev_get_priv(bus);

#if defined(CONFIG_SPI_CS_USED_GPIO)
	{
	struct dm_spi_slave_platdata *slave_plat = dev_get_parent_platdata(dev);
	u32 cs = slave_plat->cs;
	gpio_set_value(cs, !priv->flg);
	}
#elif defined(CONFIG_SPI_CS)
	{
	struct ls1x_spi_regs *const regs = priv->regs;
	if (priv->flg) {
		writeb(0x0f, &regs->softcs);
	} else {
		writeb(0xff, &regs->softcs);
	}
	}
#endif
}

static int ls1x_spi_claim_bus(struct udevice *dev)
{
	struct udevice *bus = dev->parent;
	struct ls1x_spi_priv *priv = dev_get_priv(bus);
	struct ls1x_spi_regs *const regs = priv->regs;
	u8 ret;

#if defined(CONFIG_SPI_CS_USED_GPIO)
	{
	struct dm_spi_slave_platdata *slave_plat = dev_get_parent_platdata(dev);
	u32 cs = slave_plat->cs;
	gpio_direction_output(cs, !priv->flg);
	}
#endif
	ret = readb(&regs->spcr);
	ret = ret & 0xf0;
	ret = ret | (priv->mode << 2) | (priv->div & 0x03);
	writeb(ret, &regs->spcr);

	ret = readb(&regs->sper);
	ret = ret & 0xfc;
	ret = ret | (priv->div >> 2);
	writeb(ret, &regs->sper);

	return 0;
}

static int ls1x_spi_release_bus(struct udevice *dev)
{
	return 0;
}

static int ls1x_spi_set_speed(struct udevice *bus, uint hz)
{
	struct ls1x_spi_priv *priv = dev_get_priv(bus);

	unsigned int div, div_tmp, bit;
	unsigned long clk;

#if defined(CONFIG_CPU_LOONGSON2K1000)
	clk = OSC_CLK;
#else
	clk = gd->bus_clk;
#endif
	div = DIV_ROUND_UP(clk, hz);

	if (div < 2)
		div = 2;

	if (div > 4096)
		div = 4096;

	bit = fls(div) - 1;
	switch(1 << bit) {
		case 16:
			div_tmp = 2;
			if (div > (1<<bit)) {
				div_tmp++;
			}
			break;
		case 32:
			div_tmp = 3;
			if (div > (1<<bit)) {
				div_tmp += 2;
			}
			break;
		case 8:
			div_tmp = 4;
			if (div > (1<<bit)) {
				div_tmp -= 2;
			}
			break;
		default:
			div_tmp = bit - 1;
			if (div > (1<<bit)) {
				div_tmp++;
			}
			break;
	}
	debug("clk = %ld hz = %d div_tmp = %d bit = %d\n", clk, hz, div_tmp, bit);

	priv->div = div_tmp;

	return 0;
}

static int ls1x_spi_set_mode(struct udevice *bus, uint mode)
{
	struct ls1x_spi_priv *priv = dev_get_priv(bus);

	priv->mode = mode;
	priv->flg = mode & SPI_CS_HIGH ? 1 : 0;

	return 0;
}

static int ls1x_spi_xfer(struct udevice *dev, unsigned int bitlen, const void *dout,
	     void *din, unsigned long flags)
{
	struct udevice *bus = dev->parent;
	struct ls1x_spi_priv *priv = dev_get_priv(bus);
	struct ls1x_spi_regs *const regs = priv->regs;

	const u8 *txp = dout;
	u8 *rxp = din;
	uint bytes = bitlen / 8;
	uint i;

//	debug("%s: bus:%i cs:%i bitlen:%i bytes:%i flags:%lx\n", __func__,
//		slave->bus, slave->cs, bitlen, bytes, flags);
	if (bitlen == 0)
		goto done;

	/* assume to do 8 bits transfers */
	if (bitlen % 8) {
		flags |= SPI_XFER_END;
		goto done;
	}

	if (flags & SPI_XFER_BEGIN)
		ls1x_spi_cs_activate(dev);

	/* we need to tighten the transfer loop */
	if (txp && rxp) {
		for (i = 0; i < bytes; i += 1) {
			writeb(*txp++, &regs->fifo);
			ls1x_spi_wait_txe(regs);
			*rxp++ = readb(&regs->fifo);
			ls1x_spi_wait_rxe(regs);
		}
	} else if (rxp) {
		for (i = 0; i < bytes; i += 1) {
			writeb(CONFIG_LS1X_SPI_IDLE_VAL, &regs->fifo);
			ls1x_spi_wait_txe(regs);
			*rxp++ = readb(&regs->fifo);
			ls1x_spi_wait_rxe(regs);
		}
	} else if (txp) {
		for (i = 0; i < bytes; i += 1) {
			writeb(*txp++, &regs->fifo);
			ls1x_spi_wait_txe(regs);
			readb(&regs->fifo);
			ls1x_spi_wait_rxe(regs);
		}
	} else {
		for (i = 0; i < bytes; i += 1) {
			writeb(CONFIG_LS1X_SPI_IDLE_VAL, &regs->fifo);
			ls1x_spi_wait_txe(regs);
			readb(&regs->fifo);
			ls1x_spi_wait_rxe(regs);
		}
	}

 done:
	if (flags & SPI_XFER_END)
		ls1x_spi_cs_deactivate(dev);

	return 0;
}

static int ls1x_spi_probe(struct udevice *bus)
{
	struct ls1x_spi_platdata *plat = dev_get_platdata(bus);
	struct ls1x_spi_priv *priv = dev_get_priv(bus);
	struct ls1x_spi_regs *regs;
	u8 val;

#ifdef CONFIG_DM_PCI
#ifndef CONFIG_SPL_BUILD
	if (device_is_on_pci_bus(bus)) {
		ulong iobase;
		dm_pci_read_config32(bus, PCI_BASE_ADDRESS_0, (u32 *)&iobase);
		iobase &= PCI_BASE_ADDRESS_MEM_MASK;
		iobase = dm_pci_mem_to_phys(bus, iobase);
		plat->regs = (struct ls1x_spi_regs *)iobase;

	}
#endif
#endif
	priv->regs = plat->regs;
	regs = priv->regs;

	/* 使能SPI控制器，master模式，关闭中断 */
	writeb(0x53, &regs->spcr);
	/* 清空状态寄存器 */
	writeb(0xc0, &regs->spsr);
	/* 1字节产生中断，采样(读)与发送(写)时机同时 */
	writeb(0x03, &regs->sper);
#if defined(CONFIG_SPI_CS_USED_GPIO)
	writeb(0x00, &regs->softcs);
	{
	int i;
	char name[10];
	for (i = 0; i < ARRAY_SIZE(ls1x_cs_gpio); i += 1) {
		snprintf(name, sizeof(name), "ls1x_spi%01d", i);
		gpio_request(ls1x_cs_gpio[i], name);
	}
	}
#elif defined(CONFIG_SPI_CS)
	writeb(0xff, &regs->softcs);
#endif
	/* 关闭SPI flash */
	val = readb(&regs->param);
	val &= 0xfe;
	writeb(val, &regs->param);
	/* SPI flash时序控制寄存器 */
	writeb(0x05, &regs->timing);

#if defined(CONFIG_LS1X_SPI1_ENABLE) && defined(CONFIG_CPU_LOONGSON1B)
	writel(readl(LS1X_MUX_CTRL1) | SPI1_USE_CAN | SPI1_CS_USE_PWM01, LS1X_MUX_CTRL1);
#endif

	return 0;
}

static int ls1x_spi_ofdata_to_platdata(struct udevice *bus)
{
	struct ls1x_spi_platdata *plat = dev_get_platdata(bus);

	plat->regs = map_physmem(devfdt_get_addr(bus),
				 sizeof(struct ls1x_spi_regs),
				 MAP_NOCACHE);

	return 0;
}

static const struct dm_spi_ops ls1x_spi_ops = {
	.claim_bus	= ls1x_spi_claim_bus,
	.release_bus	= ls1x_spi_release_bus,
	.xfer		= ls1x_spi_xfer,
	.set_speed	= ls1x_spi_set_speed,
	.set_mode	= ls1x_spi_set_mode,
	/*
	 * cs_info is not needed, since we require all chip selects to be
	 * in the device tree explicitly
	 */
};

static const struct udevice_id ls1x_spi_ids[] = {
	{ .compatible = "loongson,ls1x-spi" },
	{}
};

U_BOOT_DRIVER(ls1x_spi) = {
	.name	= "ls1x_spi",
	.id	= UCLASS_SPI,
	.of_match = ls1x_spi_ids,
	.ops	= &ls1x_spi_ops,
	.ofdata_to_platdata = ls1x_spi_ofdata_to_platdata,
	.platdata_auto_alloc_size = sizeof(struct ls1x_spi_platdata),
	.priv_auto_alloc_size = sizeof(struct ls1x_spi_priv),
	.probe	= ls1x_spi_probe,
};

static struct pci_device_id supported[] = {
	{ PCI_DEVICE(PCI_VENDOR_ID_LOONGSON, PCI_DEVICE_ID_LOONGSON_SPI) },
	{ }
};

U_BOOT_PCI_DEVICE(ls1x_spi, supported);
#endif
